package pkg.andru.concurrent;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * A simple thread lock implements, {@link #lock()} will make the thread wait,
 * {@link #unLock()} will wakeup wait thread. Also, supply the
 * {@link #lock(int)} and {@link #unLock(int)}, use different lockid to
 * indentify.
 * 
 * @author idiottiger
 * @version 1.0
 */
public class ThreadLocker {

	/**
	 * default lock id
	 */
	static final int DEFAULT_LOCK_ID = -1 << 14;

	final Lock lock = new ReentrantLock();

	/**
	 * default condition
	 */
	final Condition defaultCondition = lock.newCondition();

	private final Map<Integer, Condition> lockCondMap = new HashMap<Integer, Condition>();

	public ThreadLocker() {
		lockCondMap.clear();
		lockCondMap.put(DEFAULT_LOCK_ID, defaultCondition);
	}

	/**
	 * make the thread wait
	 * 
	 * @throws InterruptedException
	 *             when wake up will throw
	 */
	public void lock() throws InterruptedException {
		lock(DEFAULT_LOCK_ID);
	}

	/**
	 * wakeup the thread
	 */
	public void unLock() {
		unLock(DEFAULT_LOCK_ID);
	}

	/**
	 * use special lock id to lock, use this, you need {@link #unLock(int)} to
	 * unlock
	 * 
	 * @param lockId
	 * @throws InterruptedException
	 */
	public void lock(int lockId) throws InterruptedException {
		lock.lock();
		try {
			final Condition cond = getCondition(lockId);
			cond.await();
		} finally {
			lock.unlock();
		}
	}

	/**
	 * wakeup the thread with the lockid
	 * 
	 * @param lockId
	 * @see #lock(int)
	 */
	public void unLock(int lockId) {
		lock.lock();
		try {
			final Condition cond = getCondition(lockId);
			cond.signalAll();
		} finally {
			lock.unlock();
		}
	}

	private Condition getCondition(int lockId) {
		Condition cond = lockCondMap.get(lockId);
		if (cond == null) {
			cond = lock.newCondition();
			lockCondMap.put(lockId, cond);
		}
		return cond;
	}

}
